/*
*    Qizx/open 3.0 beta
*
* This code is the open-source version of Qizx.
* Copyright (C) 2004-2009 Axyana Software -- All rights reserved.
*
* The contents of this file are subject to the Mozilla Public License 
*  Version 1.1 (the "License"); you may not use this file except in 
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
*  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
*  for the specific language governing rights and limitations under the
*  License.
*
* The Initial Developer of the Original Code is Xavier Franc - Axyana Software.
*
*/
using System;
using EvaluationException = System.Qizx.Api.EvaluationException;
using Duration = System.Qizx.Api.Util.XQDuration;
using Conversion = System.Qizx.Xdm.Conversion;
using EvalContext = System.Qizx.XQuery.EvalContext;
using Focus = System.Qizx.XQuery.Focus;
using XQItem = System.Qizx.XQuery.XQItem;
using XQItemType = System.Qizx.XQuery.XQItemType;
using XQType = System.Qizx.XQuery.XQType;
using XQValue = System.Qizx.XQuery.XQValue;
using SingleDecimal = System.Qizx.XQuery.Dt.SingleDecimal;
using SingleDouble = System.Qizx.XQuery.Dt.SingleDouble;
using SingleDuration = System.Qizx.XQuery.Dt.SingleDuration;
using SingleFloat = System.Qizx.XQuery.Dt.SingleFloat;
namespace System.Qizx.XQuery.Fn
{
	
	/// <summary>  Implementation of function fn:avg.</summary>
	public class Avg:Function
	{
		public override Prototype[] Protos
		{
			get
			{
				return protos;
			}
			
		}
		internal static Prototype[] protos;
		
		public class Exec:Function.Call
		{
			
			public override XQValue eval(Focus focus, EvalContext context)
			{
				context.at(this);
				XQValue v = args[0].eval(focus, context);
				
				if (!v.Next())
					return System.Qizx.XQuery.XQValues.Empty;
				XQItem first = v.Item;
				
				int cnt = 1;
				double doubleSum = 0;
				float floatSum = 0;
				System.Decimal decSum = 0; //TODO null
				long intSum = 0;
				
				int currentType = first.ItemType.quickCode(), nType = 0;
				switch (currentType)
				{
					
					case XQType.QT_DOUBLE: 
					case XQType.QT_UNTYPED: 
						doubleSum = first.ValueAsDouble;
						currentType = XQType.QT_DOUBLE;
						break;
					
					case XQType.QT_FLOAT: 
						floatSum = first.ValueAsFloat;
						break;
					
					case XQType.QT_DEC: 
						decSum = first.ValueAsDecimal;
						break;
					
					case XQType.QT_INT: 
						intSum = first.ValueAsInteger;
						break;
					
					case XQType.QT_YMDUR: 
						doubleSum = first.Duration.TotalMonths;
						break;
					
					case XQType.QT_DTDUR: 
						doubleSum = first.Duration.TotalSeconds;
						break;
					
					default: 
						context.invalidArgType(this, 0, first.ItemType, null); // 
						break;
					
				}
				
				for (; ; ++cnt)
				{
					switch (currentType)
					{
						
						case XQType.QT_DOUBLE: 
							if (!v.Next())
								return new SingleDouble(doubleSum / cnt);
							nType = v.ItemType.quickCode();
							if (XQItemType.isNumeric(nType) || nType == XQType.QT_UNTYPED)
								doubleSum += v.ValueAsDouble;
							else
								context.incompatibleType(cnt, "double", this);
							break;
						
						case XQType.QT_FLOAT: 
							if (!v.Next())
								return new SingleFloat(floatSum / cnt);
							nType = v.ItemType.quickCode();
							if (nType == XQType.QT_DOUBLE || nType == XQType.QT_UNTYPED)
							{
								currentType = XQType.QT_DOUBLE;
								doubleSum = floatSum + v.ValueAsDouble;
							}
							else if (XQItemType.isNumeric(nType))
								floatSum += v.ValueAsFloat;
							else
								context.incompatibleType(cnt, "float", this);
							break;
						
						case XQType.QT_DEC: 
							if (!v.Next())
								return new SingleDecimal(Conversion.divide(decSum, cnt));
							nType = v.ItemType.quickCode();
							switch (nType)
							{
								
								case XQType.QT_UNTYPED: 
								case XQType.QT_DOUBLE: 
									currentType = XQType.QT_DOUBLE;
									doubleSum = System.Decimal.ToDouble(decSum) + v.ValueAsDouble;
									break;
								
								case XQType.QT_FLOAT: 
									currentType = nType;
									floatSum = System.Decimal.ToSingle(decSum) + v.ValueAsFloat;
									break;
								
								case XQType.QT_DEC: 
								case XQType.QT_INT: 
									decSum = System.Decimal.Add(decSum, v.ValueAsDecimal);
									break;
								
								default: 
									context.incompatibleType(cnt, "int", this);
									break;
								
							}
							break;
						
						
						case XQType.QT_INT: 
							if (!v.Next())
								return new SingleDecimal(Conversion.divide(new System.Decimal(intSum), cnt));
							nType = v.ItemType.quickCode();
							switch (nType)
							{
								
								case XQType.QT_UNTYPED: 
								case XQType.QT_DOUBLE: 
									currentType = XQType.QT_DOUBLE;
									doubleSum = intSum + v.ValueAsDouble;
									break;
								
								case XQType.QT_FLOAT: 
									currentType = nType;
									floatSum = intSum + v.ValueAsFloat;
									break;
								
								case XQType.QT_DEC: 
									currentType = nType;
									decSum = System.Decimal.Add(new System.Decimal(intSum), v.ValueAsDecimal);
									break;
								
								case XQType.QT_INT: 
									intSum += v.ValueAsInteger;
									break;
								
								default: 
									context.incompatibleType(cnt, "int", this);
									break;
								
							}
							break;
						
						
						case XQType.QT_YMDUR: 
							if (!v.Next())
							{
								return new SingleDuration(Duration.newYearMonth((int) System.Math.Round(doubleSum / cnt)));
							}
							nType = v.ItemType.quickCode();
							if (nType == currentType)
								doubleSum += v.Duration.TotalMonths;
							else
								context.incompatibleType(cnt, "yearMonthDuration", this);
							break;
						
						case XQType.QT_DTDUR: 
							if (!v.Next())
								return new SingleDuration(Duration.newDayTime(doubleSum / cnt));
							nType = v.ItemType.quickCode();
							if (nType == currentType)
								doubleSum += v.Duration.TotalSeconds;
							else
								context.incompatibleType(cnt, "dayTimeDuration", this);
							break;
						
						
						default: 
							context.invalidArgType(this, cnt, v.ItemType, null); // 
							break;
						
					}
					// e.setContext(context); throw e;	// necessary for proper backtrace
				}
			}
		}
		static Avg()
		{
			protos = new Prototype[]{Prototype.fn("avg", XQType.ATOM.opt, typeof(Exec)).arg("seq", XQType.ANY)};
		}
	}
}